// -*-C++-*-

#pragma once
#ifndef API_PROFILE_HEADER
#define API_PROFILE_HEADER

#include <cstdint>
#include <array>
#include <string>
#include <vector>
#include <arch.hpp>

#ifdef ENABLE_PROFILERS
#define __PCONCAT(x,y) x ## y
#define PCONCAT(x,y) __PCONCAT(x, y)
#define PROFILE(name)  ScopedProfiler PCONCAT(sp, __COUNTER__){name};
#else
#define PROFILE(name) /* name */
#endif

struct Sample {
  uint32_t    samp; // samples
  void*       addr; // function address
  std::string name; // function name
};

struct StackSampler
{
  // sets up stack sampling configuration and internal timer
  // the stack sampling will happen in the background afterwards
  static void begin();

  // total number of waking samples taken
  static uint64_t samples_total() noexcept;

  // total number of samples taken while asleep
  static uint64_t samples_asleep() noexcept;

  // retrieve N top results/hotspots
  static std::vector<Sample> results(int N);

  // print N top results to stdout
  static void print(int N);

  // enable or disable sample-taking
  // eg. disable sampling during stack sample result printout
  static void set_mask(bool);

  enum mode_t {
    MODE_CURRENT,
    MODE_CALLER,
    MODE_DUMMY
  };

  // set sampling mode
  static void set_mode(mode_t);
};

/**
 * @brief Scoped Profiler
 *
 * Measures the average execution time of a scope
 *
 * Example:
 * Create a ScopedProfiler in the scope that should be measuered:
 *
 * void my_function()
 * {
 *   ScopedProfiler sp;
 *
 *   do_stuff();
 *   do_more_stuff();
 * }
 *
 * Call ScopedProfiler::get_statistics() to get a formatted std::string of
 * statistics
 *
 */
class ScopedProfiler
{
 public:
  inline ScopedProfiler(const char* nme = nullptr)
    : name(nme)
  {
    record();
    // memory clobbered make sure ScopedProfilers aren't moved around
    __sw_barrier();
  }

  void record();
  ~ScopedProfiler();

  // No copying or moving
  ScopedProfiler(const ScopedProfiler&) = delete;
  ScopedProfiler& operator=(const ScopedProfiler&) = delete;
  ScopedProfiler(ScopedProfiler&&) = delete;
  ScopedProfiler& operator=(ScopedProfiler&&) = delete;

  /**
   * @brief Returns profiling statistics as a formatted std::string
   *
   * Formats the output like:
   *
   *  Average Time (us) | Samples | Function Name
   *  --------------------------------------------------------------------------------
   *         172.768148 |    1212 | Foo::my_function()
   *          40.603411 |    1223 | Foo::my_other_function()
   *          15.460829 |     602 | Bar::Bar()
   *          15.189189 |     600 | Bar::~Bar()
   *  --------------------------------------------------------------------------------
   *
   */
  static std::string get_statistics(bool sorted = true);

 private:
  uint64_t tick_start;
  const char*    name;

  struct Entry
  {
    void*       function_address;  // This identifies an Entry
                                   // function_address == 0 => the Entry is unused
    const char* name;
    std::string function_name;
    unsigned    num_samples;
    uint64_t    cycles_total;
    uint64_t    ticks_start;
	
	uint64_t cycles_average() const noexcept {
		return this->cycles_total / this->num_samples;
	}
  };


  // The guard to use, before calling rdtsc
  static enum class Guard
  {
    NOT_SELECTED,
    LFENCE,
    MFENCE,
    NOT_AVAILABLE,
  } guard;

  // Use plain array for performance (cache locality)
  // Increase size if there is a need to profile more than 64 scopes
  static std::array<Entry, 128> entries;
};

struct HeapDiag
{
  static std::string to_string();
};

#endif
